﻿using System;
using System.Collections.Generic;
using System.Linq;
using Shell.Common;
using Utk.Nlp;

namespace Utk
{
    static class Program
    {
        private readonly static List<Entry> Entires = new List<Entry>();
        private readonly static List<Link> Links = new List<Link>();
        private readonly static List<ITask> Tasks = new List<ITask>();

        static void Main(string[] args)
        {
            var fixedResponses = Utils.Enum(
                Utils.Response(Knowledge.HelloMessage, Knowledge.HelloMessage),
                Utils.Response("Ты кто?","Я Утк."));

            var initiationPhraseResponses = Knowledge.EntryTypes.Select(_ => Utils.Response(_.InitiationRequest, input => ProcessInitiationRequest(_)));
            var directStatementResponses = Knowledge.EntryTypes.Select(_ => Utils.Response(_.Classificator, input => ProcessDirectStatement(_, input)));

            var context = Utils.Flatten(
                fixedResponses,
                initiationPhraseResponses,
                directStatementResponses);
                
            while (true)
            {
                while (Tasks.Count > 0)
                {
                    var maximalTask = Tasks.SelectMax(_ => _.GetWeight());
                    Tasks.Remove(maximalTask);
                    maximalTask.Execute();
                }

                var input = User.Listen();
                var selectedResponse = context.SelectMax(_ => _.Item1.Match(input));
                selectedResponse.Item2(input);
            }
        }

        private static void AddEntry(Entry entry)
        {
            Entires.Add(entry);

            HeuristicSplitEntry(entry);
            HeuristicRequestForImmediateLinkedEntry(entry);
            HeuristicMergeEntries(entry);
        }

        private static Maybe<Entry> RetreiveLinkedEntry(YesNoRequest request, EntryType expectedType)
        {
            return request.Ask(
                () => RetreiveExpectedTypeEntry(
                    expectedType,
                    User.Listen(Knowledge.DescribeItRequest),
                    expectedType.ExpectationMismatchRequest),
                () =>
                    {
                        User.Say(Knowledge.GotItResponse);

                        return Maybe<Entry>.Nothing;
                    });
        }

        private static void HeuristicSplitEntry(Entry entry)
        {
            var linkTypes =
                entry.Types
                     .Where(_ => _.Confidence > 0)
                     .SelectMany(
                         type => entry.Types
                                      .Where(_ => _ != type)
                                      .SelectMany(
                                          _ =>
                                          Knowledge.LinkTypes.Where(
                                              linkType =>
                                              linkType.StartEntryType == type.EntryType &&
                                              linkType.EndEntryType == _.EntryType)))
                     .ToArray();

            if (linkTypes.Any())
            {
                var linkType = linkTypes.First();

                Tasks.Add(
                    new TaskSkeleton(
                        () => 1,
                        () =>
                            {
                                User.Say(string.Format("Эта фраза одновременно похожа на {0} и на {1}. Предлагаю разделить её.", linkType.StartEntryType.InternalName, linkType.EndEntryType.InternalName ));
                                new YesNoRequest(new Pattern("Будем разделять?"))
                                    .Ask(() =>
                                        {
                                            User.Say("А вот я пока не умею");
                                            return 0;
                                        });
                            }));
            }
        }

        private static void HeuristicMergeEntries(Entry entry)
        {
            var candidate = Entires
                .Where(_ => _ != entry)
                .Where(_ => Levenstein.Compute(_.Text, entry.Text) < (entry.Text.Length/7))
                .SelectMin(_ => Levenstein.Compute(_.Text, entry.Text));


           // TODO : Refactor with Maybe<>
           if (candidate != null)
           {
               var task = new TaskSkeleton(
                   () => 1,
                   () =>
                       {

                           User.Say(string.Format("\"{0}\" похоже на \"{1}\"", candidate.Text, entry.Text));

                           new YesNoRequest(new Pattern("Это значит одно и то же?"))
                               .Ask(
                                   () =>
                                       {
                                           Entires.Remove(entry);
                                           return 0;
                                       });
                       });

               Tasks.Add(task);
           }
        }

        class LinkSideSpecification
        {
            public double Priority;
            public YesNoRequest RequestForIt;
            public EntryType EntryType;
            public Func<Entry, Link> Constructor;
        }


        private static void HeuristicRequestForImmediateLinkedEntry(Entry entry)
        {
            var entryTypes = entry.Types.Where(type => type.Confidence > 0).ToArray();
            var entryTypesWithNoLinksForThem = SelectEntryTypesWithNoLinks(entry, entryTypes);
            var startToEndOptions = SelectStartToEndLinkOptions(entry, entryTypesWithNoLinksForThem, entryTypes);
            var endToStartOptions = SelectEndToStartLinkOptions(entry, entryTypesWithNoLinksForThem, entryTypes);
            var options = startToEndOptions.Concat(endToStartOptions).OrderByDescending(_ => _.Priority);

            Tasks.AddRange(
                options.Select(
                option => new TaskSkeleton(
                    () => option.Priority,
                    () =>
                        {
                            var expectedEntry = RetreiveLinkedEntry(option.RequestForIt, option.EntryType);

                            if (expectedEntry.HasValue)
                            {
                                AddLink(option.Constructor(expectedEntry));
                                AddEntry(expectedEntry);
                            }
                        })
                ));
        }

        private static EntryTypeConfidence[] SelectEntryTypesWithNoLinks(Entry entry, EntryTypeConfidence[] entryTypes)
        {
            return entryTypes
                .Where(
                    type => !Links
                                 .Where(
                                     link =>
                                     link.Type.StartEntryType == type.EntryType ||
                                     link.Type.EndEntryType == type.EntryType)
                                 .Any(link => link.Start == entry || link.End == entry))
                .ToArray();
        }

        private static IEnumerable<LinkSideSpecification> SelectEndToStartLinkOptions(Entry entry, IEnumerable<EntryTypeConfidence> entryTypesWithNoLinksForThem, IEnumerable<EntryTypeConfidence> entryTypes)
        {
            return Knowledge.LinkTypes
                            .Where(
                                linkType =>
                                entryTypesWithNoLinksForThem.Select(_ => _.EntryType)
                                                            .Contains(linkType.EndEntryType))
                            .Select(linkType => new LinkSideSpecification
                                {
                                    Priority =
                                        entryTypes.Single(
                                            type => type.EntryType == linkType.EndEntryType).Confidence,
                                    RequestForIt = linkType.EndToStartRequest,
                                    EntryType = linkType.StartEntryType,
                                    Constructor = _ => new Link
                                        {
                                            Type = linkType,
                                            End = entry,
                                            Start = _
                                        }
                                });
        }

        private static IEnumerable<LinkSideSpecification> SelectStartToEndLinkOptions(Entry entry, IEnumerable<EntryTypeConfidence> entryTypesWithNoLinksForThem, IEnumerable<EntryTypeConfidence> entryTypes)
        {
            return Knowledge.LinkTypes
                            .Where(
                                linkType =>
                                entryTypesWithNoLinksForThem.Select(_ => _.EntryType)
                                                            .Contains(linkType.StartEntryType))
                            .Select(linkType => new LinkSideSpecification
                                {
                                    Priority =
                                        entryTypes.Single(
                                            type => type.EntryType == linkType.StartEntryType)
                                                  .Confidence,
                                    RequestForIt = linkType.StartToEndRequest,
                                    EntryType = linkType.EndEntryType,
                                    Constructor = _ => new Link
                                        {
                                            Type = linkType,
                                            End = _,
                                            Start = entry
                                        }
                                });
        }

        private static void AddLink(Link link)
        {
            Links.Add(link);
        }

        private static void ProcessDirectStatement(EntryType bestFitTextClass, string input)
        {
            RetreiveExpectedTypeEntry(bestFitTextClass, input, bestFitTextClass.TypeConfirmationRequest)
                .Apply(AddEntry);
        }

        private static void ProcessInitiationRequest(EntryType bestFitInitClass)
        {
            var input = User.Listen(Knowledge.DescribeItRequest);

            RetreiveExpectedTypeEntry(bestFitInitClass, input, bestFitInitClass.ExpectationMismatchRequest)
                .Apply(AddEntry);
        }

        private static Maybe<Entry> RetreiveExpectedTypeEntry(EntryType expectedType, string input, YesNoRequest expectationMismatchRequest)
        {
            var newEntry = new Entry
            {
                Text = input,
                Types =
                        {
                            new EntryTypeConfidence
                                {
                                    EntryType = expectedType,
                                    Confidence = 1,
                                    Source = EntryTypeSource.User
                                }
                        }
            };

            newEntry.Types.AddRange(
                Knowledge.EntryTypes
                .Where(_ => _ != expectedType)
                .Select(_ => new EntryTypeConfidence
                    {
                        Confidence = _.Classificator.Match(input),
                        EntryType = _,
                        Source = EntryTypeSource.Classificator
                    }));

            if (expectedType.Classificator.Match(input) < 0.5)
            {
                return expectationMismatchRequest.Ask<Entry>(
                    () =>
                        {
                            User.Say(Knowledge.GotItResponse);

                            return newEntry;
                        },
                    () =>
                        {
                            User.Say(Knowledge.GotItResponse);

                            return Maybe<Entry>.Nothing;
                        });
            }
            else
            {
                User.Say(Knowledge.GotItResponse);
                return newEntry;
            }
        }
    }
}
